#!/usr/bin/env tsx
// @ts-nocheck
import { readFileSync, writeFileSync } from "node:fs";

/**
 * @desc 按 swagger 文档生成JSON测试 schema 类型提示文件
 * @todo 使用 go 实现, 并取消上传 testdata schema 文件
 * @param {string} DOCS_DIR - swagger 文档目录 命令行参数1
 * @param {string} TEST_DATA_SCHEMA - testdata schema 文件名 命令行参数2
 * @param {string} E2E_SCHEMA - e2e 测试用例模板 schema 文件名 命令行参数3
 * @param {string} SWAGGER_NAME - swagger 文档文件名 命令行参数4
 */

type Method = "get" | "post" | "put" | "delete" | "patch";

type SwaggerMethodOption = {
  security?: { bearer: [] }[];
  description: string;
  tags: string[];
  summary: string;
  parameters: {
    description: string;
    name: string;
    in: "header" | "body" | "path";
    required: boolean;
    type?: string;
    schema?: unknown;
  }[];
};

type Swagger = {
  paths: {
    [key: string]: {
      [method in Method]: SwaggerMethodOption;
    };
  };
};

type ApiOption = {
  path: string;
  method: Method;
  option: SwaggerMethodOption;
};

const DOCS_DIR = process.argv.at(2) ?? process.cwd();
const TEST_DATA_SCHEMA = process.argv.at(3) ?? "testdata.schema.json";
const E2E_SCHEMA = process.argv.at(4) ?? "e2e.schema.json";
const SWAGGER_NAME = process.argv.at(5) ?? "swagger.json";

main();

function main() {
  const swaggerJSONFile = readFileSync(`${DOCS_DIR}/${SWAGGER_NAME}`, "utf-8");
  const { paths }: Swagger = JSON.parse(swaggerJSONFile);
  const apiPaths = Reflect.ownKeys(paths) as string[];

  const apiSchema = {
    $schema: "http://json-schema.org/draft-07/schema#",
    oneOf: [] as { $ref: string }[],
    definitions: {},
  };

  apiPaths.forEach((path) => {
    for (const method in paths[path]) {
      const option = paths[path][method as Method];
      const schema = generateSchema({ path, method: method as Method, option });
      Object.assign(apiSchema.definitions, schema);
      apiSchema.oneOf.push({
        $ref: `#/definitions/[${method}]${path.replaceAll("/", "~1")}`,
      });
    }
  });

  writeFileSync(
    `${DOCS_DIR}/${TEST_DATA_SCHEMA}`,
    JSON.stringify(apiSchema),
    "utf-8"
  );
}

function generateSchema({ path, method, option }: ApiOption) {
  const rawPath = path.replaceAll("/", "~1");

  const e2eSchemaDefinitions = `${E2E_SCHEMA}#/definitions/`;
  const validateSchema = {
    items: {
      $ref: `#/definitions/[${method}]case${rawPath}`,
    },
  };

  const requestProperties: {
    [key in "body" | "path" | "query" | "auth" | "header"]?:
      | { $ref: string }
      | any;
  } = {};
  option.parameters.forEach(
    (
      { name, required, type, ...rest }: SwaggerMethodOption["parameters"][0],
      i
    ) => {
      switch (rest.in) {
        case "body":
          requestProperties.body = {
            $ref: `${SWAGGER_NAME}#/paths/${rawPath}/${method}/parameters/${i}/schema`,
          };
          break;
        case "header":
          if (name === "Authorization") {
            requestProperties.auth = { $ref: `${e2eSchemaDefinitions}auth` };
          } else if (type) {
            if (!requestProperties.header)
              requestProperties.header = { required: [] };
            requestProperties.header = {
              ...requestProperties.header,
              [name]: { type },
            };
            required && requestProperties.header.required.push(name);
          }
          break;
        case "path":
          requestProperties.path = {
            ...(requestProperties.path ?? {}),
            name: { type },
          };
          break;
      }
    }
  );

  return {
    [`[${method}]${path}`]: {
      allOf: [{ $ref: `${E2E_SCHEMA}#/definitions/e2e` }],
      properties: {
        path: { enum: [path] },
        method: { enum: [method.toUpperCase()] },
        argumentsValidate: validateSchema,
        businessValidate: validateSchema,
      },
    },
    [`[${method}]case${path}`]: {
      allOf: [{ $ref: `${e2eSchemaDefinitions}case` }],
      description: option.summary,
      properties: {
        request: {
          allOf: [{ $ref: `${e2eSchemaDefinitions}authRequest` }],
          properties: requestProperties,
        },
        response: {
          allOf: [{ $ref: `${e2eSchemaDefinitions}response` }],
          properties: {
            body: {
              properties: {
                code: {
                  oneOf: [{ $ref: `${e2eSchemaDefinitions}code` }],
                },
              },
            },
          },
        },
      },
    },
  };
}
